/*-------------------------------------------------------------------------
 *
 * pgarch.c
 *
 *	PostgreSQL WAL 归档程序
 *
 *	所有与归档程序相关的函数均包含在这里
 *
 *	- 由归档进程执行的所有函数
 *
 *	- 归档程序是从 postmaster 派生的，两个
 *	进程通过信号进行通信。所有由 postmaster 执行的函数均包含在此文件中。
 *
 *	初始作者: Simon Riggs		simon@2ndquadrant.com
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/postmaster/pgarch.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <time.h>
#include <sys/stat.h>
#include <unistd.h>

#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/shmem.h"
#include "storage/spin.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"


/* ----------
 * 定时器定义。
 * ----------
 */
#define PGARCH_AUTOWAKE_INTERVAL 60 /* 强制对归档状态目录进行轮询的频率；以秒为单位。 */
#define PGARCH_RESTART_INTERVAL 10	/* 尝试重启已失败归档程序的频率；以秒为单位。 */

/*
 * 尝试归档 WAL 文件时允许的最大重试次数。
 */
#define NUM_ARCHIVE_RETRIES 3

/*
 * 尝试删除孤立归档状态文件时允许的最大重试次数。
 */
#define NUM_ORPHAN_CLEANUP_RETRIES 3

/*
 * 每次目录扫描要收集的最大 .ready 文件数量。
 */
#define NUM_FILES_PER_DIRECTORY_SCAN 64

/* 归档程序进程的共享内存区域 */
typedef struct PgArchData
{
	int			pgprocno;		/* 归档程序进程的 pgprocno */

	/*
	 * 在 pgarch_readyXlog() 中强制进行目录扫描。受 arch_lck 保护。
	 */
	bool		force_dir_scan;

	slock_t		arch_lck;
} PgArchData;

char	   *XLogArchiveLibrary = "";


/* ----------
 * 本地数据
 * ----------
 */
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
static ArchiveModuleCallbacks ArchiveContext;


/*
 * 跟踪每次扫描的多个要归档的文件的信息，来自 archive_status。
 * 在有许多文件要归档时，最小化目录扫描的次数可以显著提高
 * 归档率。
 *
 * arch_heap 是在目录扫描期间使用的最大堆，用于跟踪最优先的
 * 要归档的文件。在目录扫描完成后，文件名按优先级升序
 * 存储在 arch_files 中。pgarch_readyXlog() 从 arch_files
 * 中返回文件，直到其为空，此时必须执行另一次目录扫描。
 *
 * 我们只在归档程序进程中需要这些数据，因此将其设为 palloc'd
 * 结构而不是一堆静态数组。
 */
struct arch_files_state
{
	binaryheap *arch_heap;
	int			arch_files_size;	/* arch_files[] 中的活动条目数量 */
	char	   *arch_files[NUM_FILES_PER_DIRECTORY_SCAN];
	/* 支撑堆的缓冲区，以及后来的 arch_files[]，条目： */
	char		arch_filenames[NUM_FILES_PER_DIRECTORY_SCAN][MAX_XFN_CHARS + 1];
};

static struct arch_files_state *arch_files = NULL;

/*
 * 中断处理程序设置的标志，以便在主循环中进行后续处理。
 */
static volatile sig_atomic_t ready_to_stop = false;

/* ----------
 * 本地函数前向声明
 * ----------
 */
static void fc_pgarch_waken_stop(SIGNAL_ARGS);
static void fc_pgarch_MainLoop(void);
static void fc_pgarch_ArchiverCopyLoop(void);
static bool fc_pgarch_archiveXlog(char *fc_xlog);
static bool fc_pgarch_readyXlog(char *fc_xlog);
static void fc_pgarch_archiveDone(char *fc_xlog);
static void fc_pgarch_die(int fc_code, Datum fc_arg);
static void fc_HandlePgArchInterrupts(void);
static int	fc_ready_file_comparator(Datum fc_a, Datum fc_b, void *fc_arg);
static void fc_LoadArchiveLibrary(void);
static void fc_pgarch_call_module_shutdown_cb(int fc_code, Datum fc_arg);

/* 报告 PgArchShmemInit 所需的共享内存空间 */
Size PgArchShmemSize(void)
{
	Size		fc_size = 0;

	fc_size = add_size(fc_size, sizeof(PgArchData));

	return fc_size;
}

/* 分配和初始化与归档程序相关的共享内存 */
void PgArchShmemInit(void)
{
	bool		fc_found;

	PgArch = (PgArchData *)
		ShmemInitStruct("Archiver Data", PgArchShmemSize(), &fc_found);

	if (!fc_found)
	{
		/* 第一次通过，所以初始化 */
		MemSet(PgArch, 0, PgArchShmemSize());
		PgArch->pgprocno = INVALID_PGPROCNO;
		SpinLockInit(&PgArch->arch_lck);
	}
}

/*
 * PgArchCanRestart
 *
 * 如果自上次启动以来经过的时间足够长，返回真以允许归档程序
 * 重新启动，直到达到 PGARCH_RESTART_INTERVAL。
 * 否则返回假。
 *
 * 这是一个安全阀，以防归档程序在启动时立即死亡而导致持续的
 * 重启尝试。请注意，由于我们会从后台工作进程的主循环重试
 * 启动归档程序，我们将在稍后获得另一个机会。
 */
bool PgArchCanRestart(void)
{
	static time_t fc_last_pgarch_start_time = 0;
	time_t		fc_curtime = time(NULL);

	/*
	 * 返回假，不要在距离上一个归档程序启动太久后重启归档程序。
	 */
	if ((unsigned int) (fc_curtime - fc_last_pgarch_start_time) <
		(unsigned int) PGARCH_RESTART_INTERVAL)
		return false;

	fc_last_pgarch_start_time = fc_curtime;
	return true;
}


/* 归档程序进程的主入口点 */
void PgArchiverMain(void)
{
	/*
	 * 忽略通常绑定到后台工作进程某些操作的所有信号，
	 * 除了 SIGHUP、SIGTERM、SIGUSR1、SIGUSR2 和 SIGQUIT。
	 */
	pqsignal(SIGHUP, SignalHandlerForConfigReload);
	pqsignal(SIGINT, SIG_IGN);
	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
	/* SIGQUIT 处理程序已经由 InitPostmasterChild 设置 */
	pqsignal(SIGALRM, SIG_IGN);
	pqsignal(SIGPIPE, SIG_IGN);
	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
	pqsignal(SIGUSR2, fc_pgarch_waken_stop);

	/* 重置一些由 postmaster 接受但在这里不接受的信号 */
	pqsignal(SIGCHLD, SIG_DFL);

	/* 解除信号阻塞（在 postmaster 分叉我们时信号被阻塞） */
	PG_SETMASK(&UnBlockSig);

	/* 我们不应该不必要地启动。 */
	Assert(XLogArchivingActive());

	/* 安排在归档器退出时进行清理 */
	on_shmem_exit(fc_pgarch_die, 0);

	/*
	 * 宣传我们的 pgprocno，以便后端可以使用我们的闩锁来唤醒我们
	 * 当我们在睡觉的时候。
	 */
	PgArch->pgprocno = MyProc->pgprocno;

	/* 为 pgarch_readyXlog() 创建工作区 */
	arch_files = palloc(sizeof(struct arch_files_state));
	arch_files->arch_files_size = 0;

	/* 初始化我们用于优先归档文件的最大堆。 */
	arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN,
												fc_ready_file_comparator, NULL);

	/* 加载 archive_library。 */
	fc_LoadArchiveLibrary();

	fc_pgarch_MainLoop();

	proc_exit(0);
}

/*
 * 唤醒归档器
 */
void PgArchWakeup(void)
{
	int			fc_arch_pgprocno = PgArch->pgprocno;

	/*
	 * 我们在这里不获取 ProcArrayLock。实际上这很好，因为
	 * procLatch 从未释放，所以我们只是可能会设置错误的
	 * 进程（或没有进程）的闩锁。即使在这种情况下，归档器也会
	 * 很快重新启动并开始归档。
	 */
	if (fc_arch_pgprocno != INVALID_PGPROCNO)
		SetLatch(&ProcGlobal->allProcs[fc_arch_pgprocno].procLatch);
}


/* archiver 进程的 SIGUSR2 信号处理程序 */
static void fc_pgarch_waken_stop(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	/* 设置标志以进行最后一个周期，然后关闭 */
	ready_to_stop = true;
	SetLatch(MyLatch);

	errno = fc_save_errno;
}

/*
 * pgarch_MainLoop
 *
 * 归档器的主循环
 */
static void fc_pgarch_MainLoop(void)
{
	pg_time_t	fc_last_copy_time = 0;
	bool		fc_time_to_stop;

	/*
	 * 除了等待信号之外，归档器不应该有其他事情可做……然而，归档器的存在是
	 * 为了保护我们的数据，因此她会偶尔醒来以便主动一些。
	 */
	do
	{
		ResetLatch(MyLatch);

		/* 当我们收到 SIGUSR2 时，我们会进行一次归档周期，然后退出 */
		fc_time_to_stop = ready_to_stop;

		/* 检查障碍事件和配置更新 */
		fc_HandlePgArchInterrupts();

		/*
		 * 如果我们收到了 SIGTERM，我们通常会坐着什么都不做，直到
		 * 收到 SIGUSR2。然而，这意味着随机的 SIGTERM 会
		 * 无限期地禁用归档，这似乎不是一个好主意。如果自
		 * SIGTERM 以来经过超过 60 秒，无论如何也要退出，以便
		 * 后台进程可以在需要时启动一个新的归档器。
		 */
		if (ShutdownRequestPending)
		{
			time_t		fc_curtime = time(NULL);

			if (last_sigterm_time == 0)
				last_sigterm_time = fc_curtime;
			else if ((unsigned int) (fc_curtime - last_sigterm_time) >=
					 (unsigned int) 60)
				break;
		}

		/* 做我们该做的事情 */
		fc_pgarch_ArchiverCopyLoop();
		fc_last_copy_time = time(NULL);

		/*
		 * 在接收到信号之前处于睡眠状态，或者在由于
		 * PGARCH_AUTOWAKE_INTERVAL 超过上一次复制时间而强制轮询之前，
		 * 或者在后台进程死亡之前。
		 */
		if (!fc_time_to_stop)		/* 在最后一次迭代中不要等待 */
		{
			pg_time_t	fc_curtime = (pg_time_t) time(NULL);
			int			fc_timeout;

			fc_timeout = PGARCH_AUTOWAKE_INTERVAL - (fc_curtime - fc_last_copy_time);
			if (fc_timeout > 0)
			{
				int			fc_rc;

				fc_rc = WaitLatch(MyLatch,
							   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
							   fc_timeout * 1000L,
							   WAIT_EVENT_ARCHIVER_MAIN);
				if (fc_rc & WL_POSTMASTER_DEATH)
					fc_time_to_stop = true;
			}
		}

		/*
		 * 归档器在后台进程死亡（不期望）或在收到
		 * SIGUSR2 后完成另一个归档周期时退出。
		 */
	} while (!fc_time_to_stop);
}

/*
 * pgarch_ArchiverCopyLoop
 *
 * 归档所有未完成的 xlogs 然后返回
 */
static void fc_pgarch_ArchiverCopyLoop(void)
{
	char		fc_xlog[MAX_XFN_CHARS + 1];

	/* 在第一次调用 pgarch_readyXlog() 时强制目录扫描 */
	arch_files->arch_files_size = 0;

	/*
	 * 循环遍历所有状态为 .ready 的 xlogs 并归档它们……我们主要期望
	 * 这只是一单个文件，尽管在我们仍在复制早期归档时，一些后端可能会
	 * 将文件添加到需要归档的列表中
	 */
	while (fc_pgarch_readyXlog(fc_xlog))
	{
		int			fc_failures = 0;
		int			fc_failures_orphan = 0;

		for (;;)
		{
			struct stat fc_stat_buf;
			char		fc_pathname[MAXPGPATH];

			/*
			 * 在收到 SIGTERM 后，不要启动任何更多的归档命令，或者在
			 * 后台进程意外死亡后。第一个条件是为了防止 init SIGKILL
			 * 命令，而第二个条件是为了避免与新的后台进程生成的
			 * 另一个归档器发生冲突。
			 */
			if (ShutdownRequestPending || !PostmasterIsAlive())
				return;

			/*
			 * 检查障碍事件和配置更新。这是为了确保我们会在尽快
			 * 采用新的归档命令设置，即使仍有待归档的文件 backlog。
			 */
			fc_HandlePgArchInterrupts();

			/* 如果没有配置则无法执行任何操作… */
			if (ArchiveContext.check_configured_cb != NULL &&
				!ArchiveContext.check_configured_cb())
			{
				ereport(WARNING,
						(errmsg("archive_mode enabled, yet archiving is not configured")));
				return;
			}

			/*
			 * 由于归档状态文件未以持久的方式删除，
			 * 系统崩溃可能会留下已经回收或删除的 WAL 段的 .ready 文件。
			 * 在这种情况下，只需删除孤儿状态文件并继续。这里使用
			 * unlink() 是因为即使在随后的崩溃中，相同的孤儿文件也会被移除，
			 * 所以无需担心持久性。
			 */
			snprintf(fc_pathname, MAXPGPATH, XLOGDIR "/%s", fc_xlog);
			if (stat(fc_pathname, &fc_stat_buf) != 0 && errno == ENOENT)
			{
				char		fc_xlogready[MAXPGPATH];

				StatusFilePath(fc_xlogready, fc_xlog, ".ready");
				if (unlink(fc_xlogready) == 0)
				{
					ereport(WARNING,
							(errmsg("removed orphan archive status file \"%s\"",
									fc_xlogready)));

					/* 离开循环并移动到下一个状态文件 */
					break;
				}

				if (++fc_failures_orphan >= NUM_ORPHAN_CLEANUP_RETRIES)
				{
					ereport(WARNING,
							(errmsg("removal of orphan archive status file \"%s\" failed too many times, will try again later",
									fc_xlogready)));

					/* 放弃清理孤儿状态文件 */
					return;
				}

				/* 等待一段时间再重试 */
				pg_usleep(1000000L);
				continue;
			}

			if (fc_pgarch_archiveXlog(fc_xlog))
			{
				/* 成功 */
				fc_pgarch_archiveDone(fc_xlog);

				/*
				 * 告诉累积统计系统我们成功归档的 WAL 文件
				 */
				pgstat_report_archiver(fc_xlog, false);

				break;			/* 离开内部重试循环 */
			}
			else
			{
				/*
				 * 告诉累计统计系统关于我们未能归档的WAL文件
				 */
				pgstat_report_archiver(fc_xlog, true);

				if (++fc_failures >= NUM_ARCHIVE_RETRIES)
				{
					ereport(WARNING,
							(errmsg("archiving write-ahead log file \"%s\" failed too many times, will try again later",
									fc_xlog)));
					return;		/* 现在放弃归档 */
				}
				pg_usleep(1000000L);	/* 等待一段时间再重试 */
			}
		}
	}
}

/*
 * pgarch_archiveXlog
 *
 * 调用 archive_file_cb 将一个归档文件复制到应该去的地方
 *
 * 如果成功则返回 true
 */
static bool fc_pgarch_archiveXlog(char *fc_xlog)
{
	char		fc_pathname[MAXPGPATH];
	char		fc_activitymsg[MAXFNAMELEN + 16];
	bool		fc_ret;

	snprintf(fc_pathname, MAXPGPATH, XLOGDIR "/%s", fc_xlog);

	/* 在 PS 显示中报告归档活动 */
	snprintf(fc_activitymsg, sizeof(fc_activitymsg), "archiving %s", fc_xlog);
	set_ps_display(fc_activitymsg);

	fc_ret = ArchiveContext.archive_file_cb(fc_xlog, fc_pathname);
	if (fc_ret)
		snprintf(fc_activitymsg, sizeof(fc_activitymsg), "last was %s", fc_xlog);
	else
		snprintf(fc_activitymsg, sizeof(fc_activitymsg), "failed on %s", fc_xlog);
	set_ps_display(fc_activitymsg);

	return fc_ret;
}

/*
 * pgarch_readyXlog
 *
 * 返回尚未被归档的最旧 xlog 文件的名称。
 * 不会设置通知，表明文件归档正在进行，因此
 * 如果创建了多个并发归档任务，则需要扩展此功能。如果发生故障，
 * 我们将在下一个可用机会完全重新复制该文件。
 *
 * 返回最旧的文件很重要，以便我们按顺序归档 xlogs，
 * 有两个原因：
 * 1) 维护恢复所需的 xlogs 的顺序链
 * 2) 因为最旧的文件会更快成为检查点时重复使用的候选者
 *
 * 注意：“最旧”比较将任何 .history 文件视为比
 * 除另一个 .history 文件外的任何其他文件都旧；带有较小 ID 的时间线
 * 上的段将比带有较大 ID 的时间线上的所有段都旧；
 * 最终结果是过去的时间线在归档时享有更高的优先级。
 * 这似乎没问题，或者至少没有明显值得改变。
 */
static bool fc_pgarch_readyXlog(char *fc_xlog)
{
	char		fc_XLogArchiveStatusDir[MAXPGPATH];
	DIR		   *fc_rldir;
	struct dirent *fc_rlde;
	bool		fc_force_dir_scan;

	/*
	 * 如果请求了目录扫描，清除存储的文件名并继续。
	 */
	SpinLockAcquire(&PgArch->arch_lck);
	fc_force_dir_scan = PgArch->force_dir_scan;
	PgArch->force_dir_scan = false;
	SpinLockRelease(&PgArch->arch_lck);

	if (fc_force_dir_scan)
		arch_files->arch_files_size = 0;

	/*
	 * 如果我们仍然有来自上一个目录扫描的存储文件名，
	 * 尝试返回其中一个。我们检查以确保状态文件仍然存在，
	 * 因为之前文件的 archive_command 可能已经标记为完成。
	 */
	while (arch_files->arch_files_size > 0)
	{
		struct stat fc_st;
		char		fc_status_file[MAXPGPATH];
		char	   *fc_arch_file;

		arch_files->arch_files_size--;
		fc_arch_file = arch_files->arch_files[arch_files->arch_files_size];
		StatusFilePath(fc_status_file, fc_arch_file, ".ready");

		if (stat(fc_status_file, &fc_st) == 0)
		{
			strcpy(fc_xlog, fc_arch_file);
			return true;
		}
		else if (errno != ENOENT)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m", fc_status_file)));
	}

	/* arch_heap 可能为空，但我们还是要确保 */
	binaryheap_reset(arch_files->arch_heap);

	/*
	 * 打开归档状态目录并读取带有 .ready 后缀的文件列表，
	 * 寻找最早的文件。
	 */
	snprintf(fc_XLogArchiveStatusDir, MAXPGPATH, XLOGDIR "/archive_status");
	fc_rldir = AllocateDir(fc_XLogArchiveStatusDir);

	while ((fc_rlde = ReadDir(fc_rldir, fc_XLogArchiveStatusDir)) != NULL)
	{
		int			fc_basenamelen = (int) strlen(fc_rlde->d_name) - 6;
		char		fc_basename[MAX_XFN_CHARS + 1];
		char	   *fc_arch_file;

		/* 忽略意外字符数的条目 */
		if (fc_basenamelen < MIN_XFN_CHARS ||
			fc_basenamelen > MAX_XFN_CHARS)
			continue;

		/* 忽略意外字符的条目 */
		if (strspn(fc_rlde->d_name, VALID_XFN_CHARS) < fc_basenamelen)
			continue;

		/* 忽略任何不以 .ready 结尾的条目 */
		if (strcmp(fc_rlde->d_name + fc_basenamelen, ".ready") != 0)
			continue;

		/* 截掉 .ready 后缀 */
		memcpy(fc_basename, fc_rlde->d_name, fc_basenamelen);
		fc_basename[fc_basenamelen] = '\0';

		/*
		 * 如果文件具有足够高的优先级，则将其存储在我们的最大堆中。
		 */
		if (arch_files->arch_heap->bh_size < NUM_FILES_PER_DIRECTORY_SCAN)
		{
			/* 如果堆还没有满，快速添加它。 */
			fc_arch_file = arch_files->arch_filenames[arch_files->arch_heap->bh_size];
			strcpy(fc_arch_file, fc_basename);
			binaryheap_add_unordered(arch_files->arch_heap, CStringGetDatum(fc_arch_file));

			/* 如果我们刚填满堆，将其设置为有效。 */
			if (arch_files->arch_heap->bh_size == NUM_FILES_PER_DIRECTORY_SCAN)
				binaryheap_build(arch_files->arch_heap);
		}
		else if (fc_ready_file_comparator(binaryheap_first(arch_files->arch_heap),
									   CStringGetDatum(fc_basename), NULL) > 0)
		{
			/*
			 * 移除优先级最低的文件，并将当前文件添加到堆中。
			 */
			fc_arch_file = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap));
			strcpy(fc_arch_file, fc_basename);
			binaryheap_add(arch_files->arch_heap, CStringGetDatum(fc_arch_file));
		}
	}
	FreeDir(fc_rldir);

	/* 如果没有找到文件，简单返回。 */
	if (arch_files->arch_heap->bh_size == 0)
		return false;

	/*
	 * 如果我们没有填满堆，那就没有使其有效。现在执行此操作。
	 */
	if (arch_files->arch_heap->bh_size < NUM_FILES_PER_DIRECTORY_SCAN)
		binaryheap_build(arch_files->arch_heap);

	/*
	 * 按优先级升序填充 arch_files 数组中的文件，以便归档。
	 */
	arch_files->arch_files_size = arch_files->arch_heap->bh_size;
	for (int fc_i = 0; fc_i < arch_files->arch_files_size; fc_i++)
		arch_files->arch_files[fc_i] = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap));

	/* 返回优先级最高的文件。 */
	arch_files->arch_files_size--;
	strcpy(fc_xlog, arch_files->arch_files[arch_files->arch_files_size]);

	return true;
}

/*
 * ready_file_comparator
 *
 * 比较给定要归档文件的归档优先级。如果“a”
 * 的优先级高于“b”，将返回负值。如果“b”
 * 的优先级高于“a”，将返回正值。
 * 如果“a”和“b”具有相同的值，将返回0。
 */
static int fc_ready_file_comparator(Datum fc_a, Datum fc_b, void *fc_arg)
{
	char	   *fc_a_str = DatumGetCString(fc_a);
	char	   *fc_b_str = DatumGetCString(fc_b);
	bool		fc_a_history = IsTLHistoryFileName(fc_a_str);
	bool		fc_b_history = IsTLHistoryFileName(fc_b_str);

	/* 时间线历史文件始终具有最高优先级。 */
	if (fc_a_history != fc_b_history)
		return fc_a_history ? -1 : 1;

	/* 较旧的文件优先级更高。 */
	return strcmp(fc_a_str, fc_b_str);
}

/*
 * PgArchForceDirScan
 *
 * 调用时，下一次调用 pgarch_readyXlog() 将执行
 * 目录扫描。这对于确保像时间线历史文件这样的
 * 重要文件尽快归档非常有用。
 */
void PgArchForceDirScan(void)
{
	SpinLockAcquire(&PgArch->arch_lck);
	PgArch->force_dir_scan = true;
	SpinLockRelease(&PgArch->arch_lck);
}

/*
 * pgarch_archiveDone
 *
 * 发出通知，表明 xlog 文件已成功归档。
 * 我们通过将状态文件从 NNN.ready 重命名为 NNN.done 来实现。
 * 最终，一个检查点进程将注意到这一点并删除 NNN.done 文件和
 * xlog 文件本身。
 */
static void fc_pgarch_archiveDone(char *fc_xlog)
{
	char		fc_rlogready[MAXPGPATH];
	char		fc_rlogdone[MAXPGPATH];

	StatusFilePath(fc_rlogready, fc_xlog, ".ready");
	StatusFilePath(fc_rlogdone, fc_xlog, ".done");
	(void) durable_rename(fc_rlogready, fc_rlogdone, WARNING);
}


/*
 * pgarch_die
 *
 * 退出时清理处理程序
 */
static void fc_pgarch_die(int fc_code, Datum fc_arg)
{
	PgArch->pgprocno = INVALID_PGPROCNO;
}

/*
 * WAL 归档进程的中断处理程序。
 *
 * 这在 pgarch_MainLoop 和 pgarch_ArchiverCopyLoop 中调用。
 * 它检查障碍事件、配置更新和对内存上下文日志记录的请求，
 * 但不对关闭请求进行处理，因为处理
 * 关闭请求的方式在这些循环中不同。
 */
static void fc_HandlePgArchInterrupts(void)
{
	if (ProcSignalBarrierPending)
		ProcessProcSignalBarrier();

	/* 执行此进程的内存上下文日志记录 */
	if (LogMemoryContextPending)
		ProcessLogMemoryContextInterrupt();

	if (ConfigReloadPending)
	{
		char	   *fc_archiveLib = pstrdup(XLogArchiveLibrary);
		bool		fc_archiveLibChanged;

		ConfigReloadPending = false;
		ProcessConfigFile(PGC_SIGHUP);

		fc_archiveLibChanged = strcmp(XLogArchiveLibrary, fc_archiveLib) != 0;
		pfree(fc_archiveLib);

		if (fc_archiveLibChanged)
		{
			/*
			 * 理想情况下，我们只需卸载先前的归档模块并
			 * 加载新的模块，但目前没有卸载库的机制
			 * （见上面关于 internal_load_library() 的注释）。
			 * 为了处理这个问题，我们只是重新启动
			 * 归档器。新的归档模块将在新的
			 * 归档器进程启动时加载。注意，这会触发
			 * 如果定义的模块的关闭回调。
			 */
			ereport(LOG,
					(errmsg("restarting archiver process because value of "
							"\"archive_library\" was changed")));

			proc_exit(0);
		}
	}
}

/*
 * LoadArchiveLibrary
 *
 * 将归档回调加载到我们的本地 ArchiveContext 中。
 */
static void fc_LoadArchiveLibrary(void)
{
	ArchiveModuleInit fc_archive_init;

	memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));

	/*
	 * 如果启用了外壳归档，请使用我们的特殊初始化函数。
	 * 否则，加载库并调用其 _PG_archive_module_init()。
	 */
	if (XLogArchiveLibrary[0] == '\0')
		fc_archive_init = shell_archive_init;
	else
		fc_archive_init = (ArchiveModuleInit)
			load_external_function(XLogArchiveLibrary,
								   "_PG_archive_module_init", false, NULL);

	if (fc_archive_init == NULL)
		ereport(ERROR,
				(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));

	(*fc_archive_init) (&ArchiveContext);

	if (ArchiveContext.archive_file_cb == NULL)
		ereport(ERROR,
				(errmsg("archive modules must register an archive callback")));

	before_shmem_exit(fc_pgarch_call_module_shutdown_cb, 0);
}

/*
 * 如果定义了已加载归档模块的关闭回调，请调用它。
 */
static void fc_pgarch_call_module_shutdown_cb(int fc_code, Datum fc_arg)
{
	if (ArchiveContext.shutdown_cb != NULL)
		ArchiveContext.shutdown_cb();
}
